// 
// Copyright (c) 2006-2008 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;

namespace Anculus.Core.Extended
{
	public class FileLogBackend : ILogBackend, IEquatable<FileLogBackend>
	{
		private string _filename;
		private int _maxLogSize;
		private int _maxArchives;
		
		private object _sync = new object ();
		
		public FileLogBackend (string filename)
			: this (filename, 512, true, 3)
		{
		}
		
		public FileLogBackend (string filename, bool append)
			: this (filename, 512, append, 3)
		{
		}
		
		public FileLogBackend (string filename, int maxLogSize)
			: this (filename, maxLogSize, true, 3)
		{
		}
		
		public FileLogBackend (string filename, int maxLogSize, bool append, int maxArchives)
		{
			if (filename == null)
				throw new ArgumentNullException ("filename");
			if (!append && File.Exists (filename))
				File.Delete (filename);
			
			this._filename = filename;
			MaximumLogSize = maxLogSize;
			MaximumArchives = maxArchives;
		}
		
		public string Filename
		{
			get { return _filename; }
		}

		public int MaximumLogSize
		{
			get { return _maxLogSize / 1024; }
			set {
				if (value < 1)
					throw new ArgumentException ("maxLogSize must be at least 1 kB.");
				_maxLogSize = value * 1024;
			}
		}

		public int MaximumArchives
		{
			get { return _maxArchives; }
			set {
				if (value < 1)
					throw new ArgumentException ("maxArchives must be at least 1.");
				_maxArchives = value;
			}
		}
		
		public void Log (string timestamp, LogLevel level, string logger, object message, Exception ex)
		{
			lock (_sync) {
				if (File.Exists (_filename)) {
					FileInfo fi = new FileInfo (_filename);
					if (fi.Length > _maxLogSize)
						ArchiveLog ();
				}
				
				using (FileStream stream = new FileStream (_filename, FileMode.Append, FileAccess.Write)) {
					using (StreamWriter writer = new StreamWriter (stream)) {
						writer.Write (timestamp);
					
						switch (level)
						{
							case LogLevel.Debug:
								writer.Write (" [DEBUG] ");
								break;
							case LogLevel.Info:
								writer.Write (" [INFO] ");
								break;
							case LogLevel.Warning:
								writer.Write (" [WARNING] ");
								break;
							case LogLevel.Error:
								writer.Write (" [ERROR] ");
								break;
							case LogLevel.Fatal:
								writer.Write (" [FATAL] ");
								break;
						}
						
						if (logger != null) {
							writer.Write ("[");
							writer.Write (logger);
							writer.Write ("] ");
						}
			
						if (message != null)
							writer.WriteLine (message.ToString ());
						else
							writer.WriteLine ();
			
						if (ex != null)
						{
							writer.WriteLine (ex.Message);
							writer.WriteLine (ex.StackTrace);
			
							if (ex.InnerException != null) {
								writer.WriteLine (ex.InnerException.Message);
								writer.WriteLine (ex.InnerException.StackTrace);
							}
						}
					}
				}
			}
		}
		
		public override bool Equals (object obj)
		{
			if (obj == null)
				return false;
			
			FileLogBackend fb = obj as FileLogBackend;
			return fb != null && fb._filename == _filename;
		}
		
		public bool Equals (FileLogBackend other)
		{
			return other != null && other._filename == _filename;
		}
		
		public override int GetHashCode ()
		{
			return typeof (FileLogBackend).GetHashCode ();
		}
		
		private void ArchiveLog ()
		{
			string previousArchiveName = null;
			for (int i=_maxArchives; i >= 0; i--) {
				string archiveName = String.Concat (_filename, ".", i);

				if (i == _maxArchives) {
					//the last archive, must be deleted if it exists
					if (File.Exists (archiveName))
						File.Delete (archiveName);
				} else if (i == 0) {
					//the log file itself must be archived
					if (File.Exists (_filename))
						File.Move (_filename, previousArchiveName);
				} else {
					//the archive must be moved
					if (File.Exists (archiveName))
						File.Move (archiveName, previousArchiveName);
				}
				
				previousArchiveName = archiveName;
			}
		}
	}
}